package com.netty.server.handler;

import com.netty.server.cmd.ByteCmd;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.TooLongFrameException;
import lombok.extern.slf4j.Slf4j;

import java.nio.ByteOrder;

/**
 * 自定义分包器
 * 参考: https://zhuanlan.zhihu.com/p/95621344
 */
@Slf4j
public class TankFrameDecoder extends LengthFieldBasedFrameDecoder {

    /**
     * 最长包长度
     */
    private static final int MAX_FRAME_LENGTH = 0xFFFF;
    /**
     * 长度字段偏移量
     * 1: 如果是{@link ByteCmd}
     * 偏移量 = header(1) + cmdType(1) + cmdId(4) + needAck(1) = 6
     * 2: 如果是{@link com.netty.server.cmd.ByteResCmd}
     * 偏移量 = header(1) + cmdType(1) + cmdId(4) + resCode(1) = 6
     */
    private static final int LENGTH_FIELD_OFFSET = 6;
    /**
     * 长度字段数据长度（单位：byte）
     */
    private static final int LENGTH_FIELD_LENGTH = 2;
    /**
     * 长度补偿量。由于不存在包尾，这里指定0，代表长度字段的值+0
     */
    private static final int LENGTH_ADJUSTMENT = 1;
    /**
     * 结果去除几个字节。这里指定0，表示数据包原封不动
     */
    private static final int INITIAL_BYTES_TO_STRIP = 0;
    /**
     * 快速失败开关。这打开代表一旦发现大于MAX_FRAME_LENGTH的长度，就抛出TooLongFrameException异常
     */
    private static final boolean FAIL_FAST = true;

    public TankFrameDecoder() {
        super(ByteOrder.LITTLE_ENDIAN, MAX_FRAME_LENGTH,
                LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT,
                INITIAL_BYTES_TO_STRIP, FAIL_FAST);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 忽略TooLongFrameException之外的异常
        if (!(cause instanceof TooLongFrameException)) {
            super.exceptionCaught(ctx, cause);
            return;
        }
        log.error("发现异常过长的数据包, 断开连接!", cause);
        ctx.pipeline().close();
    }
}
